﻿using Adnc.Application.Shared.Dtos;
using Adnc.Application.Shared.Enum;
using Adnc.Application.Shared.Services;
using Adnc.Infra.Caching;
using Adnc.Infra.Helper;
using Adnc.Infra.IRepositories;
using Adnc.Shared.RpcServices.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using WYT.Usr.Application.Contracts.Dtos.Wallet;
using WYT.Usr.Application.Contracts.Services;
using WYT.Usr.Repository.Entities;

namespace WYT.Usr.Application.Services
{
    /// <summary>
    /// 
    /// </summary>
    public class WalletAppService : AbstractAppService, IWalletAppService
    {
        private readonly IEfRepository<Wallet> _walletRepository;

        private readonly IEfRepository<WalletLog> _walletLogRepository;

        private readonly IEfRepository<WalletTransferAccountsLog> _walletTransferAccountsLog;

        private readonly IEfRepository<User> _userRepository;

        private readonly IPayRpcService _payRpcService;

        private readonly IEfRepository<WithdrawalsRecord> _withdrawalsRecordRepository;

        private readonly IDistributedLocker _locker;

        private readonly IRedisProvider _redisProvider;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="walletRepository"></param>
        /// <param name="walletLogRepository"></param>
        /// <param name="withdrawalsRecordRepository"></param>
        /// <param name="userRepository"></param>
        /// <param name="walletTransferAccountsLog"></param>
        /// <param name="payRpcService"></param>
        /// <param name="locker"></param>
        /// <param name="redisProvider"></param>
        public WalletAppService(IEfRepository<Wallet> walletRepository
            , IEfRepository<WalletLog> walletLogRepository
            , IEfRepository<WithdrawalsRecord> withdrawalsRecordRepository
            , IEfRepository<User> userRepository
            , IEfRepository<WalletTransferAccountsLog> walletTransferAccountsLog
            , IPayRpcService payRpcService
            , IDistributedLocker locker
            , IRedisProvider redisProvider)
        {
            _walletRepository = walletRepository;
            _walletLogRepository = walletLogRepository;
            _withdrawalsRecordRepository = withdrawalsRecordRepository;
            _userRepository = userRepository;
            _walletTransferAccountsLog = walletTransferAccountsLog;
            _payRpcService = payRpcService;
            _locker = locker;
            _redisProvider = redisProvider;
        }

        /// <summary>
        /// 获取用户钱包
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<WalletDto>> GetUserWallet(long userId)
        {
            var data = await _walletRepository.FindAsync(c => c.UserId == userId);
            if (data == null)
                return new WalletDto();
            else
                return new WalletDto
                {
                    NoChargeBalance = data.NoChargeBalance,
                    ShoppingMoney = data.ShoppingMoney,
                    ChargeBalance = data.ChargeBalance,
                    Balance = data.Balance
                };
        }

        /// <summary>
        /// 支付
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<bool>> WalletPayment(WalletPaymentDto input)
        {
            var user = await _userRepository.FindAsync(input.UserId);
            if (user == null || user.State == 1 || user.State == 2)
                return false;

            var cacheKey = "wallet:" + user.Id;
            var flag = await _locker.LockAsync(cacheKey);
            if (!flag.Success)
            {
                await Task.Delay(300);
                flag = await _locker.LockAsync(cacheKey);
            }

            try
            {
                var wallet = await _walletRepository.FindAsync(c => c.UserId == input.UserId);
                if (wallet == null)
                    return false;
                WalletLog walletLog = new WalletLog
                {
                    Id = IdGenerater.GetNextId(),
                    UserId = user.Id,
                    Mode = 3,
                    ChangeAmount = input.Amount,                    
                    ChangeMode = -1,
                    Amount = -input.Amount,
                    ChangeFrontImage = JsonSerializer.Serialize(wallet),
                    Describe = "消费",
                    SourceOrder = input.OrderId,
                    CreateTime = DateTime.Now
                };
                //余额支付
                if (input.PaidMode == (int)EnumPayMode.BALANCE)
                {
                    if (wallet.Balance < input.Amount)
                        return false;

                    walletLog.ChangeFrontMoney = wallet.Balance;
                    if (wallet.ChargeBalance >= input.Amount)
                        wallet.ChargeBalance -= input.Amount;
                    else
                    {
                        wallet.NoChargeBalance -= (input.Amount - wallet.ChargeBalance);
                        wallet.ChargeBalance = 0;
                    }
                    wallet.Balance -= input.Amount;
                    walletLog.WalletType = 1;
                    walletLog.ChangeLaterMoney = wallet.Balance;
                }
                else if (input.PaidMode == (int)EnumPayMode.BONUS)
                {
                    if (wallet.ShoppingMoney < input.Amount)
                        return false;
                    else
                    {
                        walletLog.ChangeFrontMoney = wallet.ShoppingMoney;
                        wallet.ShoppingMoney -= input.Amount;
                        walletLog.WalletType = 2;
                        walletLog.ChangeLaterMoney = wallet.ShoppingMoney;
                    }
                }

                walletLog.ChangedLaterImage = JsonSerializer.Serialize(wallet);
                await _walletRepository.UpdateAsync(wallet, UpdatingProps<Wallet>(c => c.NoChargeBalance, c => c.ShoppingMoney, c => c.ChargeBalance, c => c.Balance));
                await _walletLogRepository.InsertAsync(walletLog);
                return true;
            }
            finally
            {
                await _locker.SafedUnLockAsync(cacheKey, flag.LockValue);
            }
        }

        /// <summary>
        /// 钱包日志
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<PageModelDto<WalletLogListDto>>> GetWalletLogs(long userId, WalletLogSearchPagedDto input)
        {
            var expression = ExpressionCreator.New<WalletLog>().And(x => x.UserId == userId)
                .AndIf(input.Mode > 0, x => x.Mode == input.Mode)
                .AndIf(input.Type > 0, x => x.WalletType == input.Type)
                .AndIf(input.StartTime.HasValue, x => x.CreateTime >= input.StartTime)
                .AndIf(input.EndTime.HasValue, x => x.CreateTime <= input.EndTime);

            var count = await _walletLogRepository.CountAsync(expression);
            var data = await _walletLogRepository.Where(expression)
                .OrderByDescending(c => c.CreateTime)
                .Skip(input.PageSize * (input.PageIndex - 1))
                .Take(input.PageSize)
                .Select(c => new WalletLogListDto
                {
                    Describe = c.Describe,
                    ChangeAmount = c.ChangeAmount,
                    ChangeMode = c.ChangeMode,
                    CreateTime = c.CreateTime,
                }).ToListAsync();
            return new PageModelDto<WalletLogListDto>(input.PageIndex, input.PageSize, data, count);
        }

        /// <summary>
        /// 获取用户收益
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<UseIncomeDto>> GetIncomeInfoAsync(long userId, DateTime start, DateTime end)
        {
            var result = new UseIncomeDto { };
            result.Income = await _walletLogRepository.Where(x => x.UserId == userId && x.CreateTime >= start && x.CreateTime < end && x.Mode == 2).SumAsync(x => x.Amount);
            result.OrderCount = await _walletLogRepository.Where(x => x.UserId == userId && x.CreateTime >= start && x.CreateTime < end && x.Mode == 2).GroupBy(x => x.SourceOrder).CountAsync();
            return result;
        }

        /// <summary>
        /// 用户提现申请
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<bool>> UserWithdrawalApplication(long userId, WithdrawalDto input)
        {
            if (input.Amount < 100)
                return Problem(null, "提现金额不能低于100");
            if (input.Amount > 50000)
                return Problem(null, "提现金额不能大于50000");

            var user = await _userRepository.FindAsync(userId);
            var inputPass = HashHelper.GetHashedString(HashType.MD5, input.Pass);
            if (!user.WithdrawalPassword.Equals(inputPass))
                return Problem(HttpStatusCode.BadRequest, "支付密码错误");

            if (user.State == 1 || user.State == 2)
                return Problem(HttpStatusCode.BadRequest, "账号处于冻结状态，不可提现，请联系客服！");

            var bankResponse = await _payRpcService.GetBankCardById(input.BankCardId);
            if (!bankResponse.IsSuccessStatusCode)
            {
                if(bankResponse.StatusCode == HttpStatusCode.NotFound)
                    return Problem(HttpStatusCode.InternalServerError, "银行卡信息错误");
                else
                    return Problem(HttpStatusCode.InternalServerError, "服务异常");
            }
                
            var bank = bankResponse.Content;
            if (bank.UserId != userId || bank.State != 1)
                return Problem(HttpStatusCode.BadRequest, "银行卡信息错误或者不可用");

            var cacheKey = "wallet:" + userId;
            var flag = await _locker.LockAsync(cacheKey);
            if (!flag.Success)
            {
                await Task.Delay(300);
                flag = await _locker.LockAsync(cacheKey);
            }

            try
            {
                var wallet = await _walletRepository.FindAsync(c => c.UserId == userId);
                if (wallet == null || wallet.Balance < input.Amount)
                    return Problem(HttpStatusCode.BadRequest, "余额不足");
                WalletLog walletLog = new WalletLog
                {
                    Id = IdGenerater.GetNextId(),
                    UserId = userId,
                    Mode = 1,
                    ChangeAmount = input.Amount,
                    ChangeMode = -1,
                    Amount = -input.Amount,
                    ChangeFrontImage = JsonSerializer.Serialize(wallet),
                    WalletType = 1,
                    Describe = "提现",
                    CreateTime = DateTime.Now,
                    ChangeFrontMoney = wallet.Balance
                };

                var records = _withdrawalsRecordRepository.Where(c => c.UserId == userId && c.ApplyDate == DateTime.Now.Date && c.BankCardId == input.BankCardId).OrderByDescending(c => c.CreateTime).ToList();
                var record = records.FirstOrDefault(c => c.Amount + input.Amount <= 50000 && c.BankCardId == bank.Id);
                if (record == null)
                {
                    record = new WithdrawalsRecord()
                    {
                        ApplyDate = DateTime.Now.Date,
                        UserId = userId,
                        Amount = input.Amount,
                        BankCardId = bank.Id,
                        BankCardImage = JsonSerializer.Serialize(bank),
                        CreateTime = DateTime.Now,
                        State = 0,
                    };
                }
                else
                    record.Amount += input.Amount;

                decimal fee = 0;
                if (wallet.NoChargeBalance >= input.Amount)
                {
                    wallet.NoChargeBalance -= input.Amount;
                    record.NoChargeBalance += input.Amount;
                }
                else
                {
                    record.NoChargeBalance += wallet.NoChargeBalance;
                    record.ChargeBalance += input.Amount - wallet.NoChargeBalance;

                    wallet.ChargeBalance -= (input.Amount - wallet.NoChargeBalance);
                    wallet.NoChargeBalance = 0;
                    fee = Math.Round(record.ChargeBalance * 0.05m, 2);
                }
                record.Fee = fee;
                record.ActualAmount = record.Amount - fee;
                wallet.Balance -= input.Amount;
                walletLog.ChangeLaterMoney = wallet.Balance;
                walletLog.ChangedLaterImage = JsonSerializer.Serialize(wallet);
                if (record.Id == 0)
                {
                    record.Id = IdGenerater.GetNextId();
                    await _withdrawalsRecordRepository.InsertAsync(record);
                }
                else
                    await _withdrawalsRecordRepository.UpdateAsync(record, UpdatingProps<WithdrawalsRecord>(x => x.Amount, x => x.ActualAmount, x => x.NoChargeBalance, x => x.ChargeBalance, x => x.Fee));

                walletLog.SourceOrder = record.Id;

                await _walletRepository.UpdateAsync(wallet, UpdatingProps<Wallet>(c => c.NoChargeBalance, c => c.ChargeBalance, c => c.Balance));
                await _walletLogRepository.InsertAsync(walletLog);
                return true;
            }
            finally
            {
                await _locker.SafedUnLockAsync(cacheKey, flag.LockValue);
            }
        }

        /// <summary>
        /// 转账
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<bool>> TransferAccounts(long userId, WalletTransferAccountsDto input)
        {
            //转出人信息
            var Transferor = await _userRepository.FindAsync(userId);
            //收款人
            var Transferee = await _userRepository.FindAsync(input.Payee);
            if (Transferee == null)
                return Problem(null,"收款人不存在");
            if (Transferor.State == 1 || Transferor.State == 2)
                return Problem(null, "被冻结的用户无法转账");
            if (string.IsNullOrEmpty(Transferor.WithdrawalPassword))
                return Problem(null, "请设置交易密码");

            var data = await _redisProvider.StringGetAsync("applet" + Transferor.Id + "WrongPayPasswordCount");
            if (string.IsNullOrEmpty(data))
                await _redisProvider.StringSetAsync("applet" + Transferor.Id + "WrongPayPasswordCount", "0", new TimeSpan(1, 0, 0));
            else if (int.Parse(data) >=  5)
                return Problem(null, "交易密码连续错误5次，24小时内不能再使用");
            var inputPass = HashHelper.GetHashedString(HashType.MD5, input.Pass);
            if (!Transferor.WithdrawalPassword.Equals(inputPass))
            {
                await _redisProvider.IncrByAsync("applet" + Transferor.Id + "WrongPayPasswordCount");
                return Problem(HttpStatusCode.BadRequest, "支付密码错误");
            }
            await _redisProvider.KeyDelAsync("applet" + Transferor.Id + "WrongPayPasswordCount");

            var fromWallet = await _walletRepository.FindAsync(x => x.UserId == Transferor.Id);
            if(fromWallet == null || fromWallet.Balance < input.Money)
                return Problem(HttpStatusCode.BadRequest, "余额不足");
            var toWallet = await _walletRepository.FindAsync(x => x.UserId == Transferee.Id);

            var transferLog = new WalletTransferAccountsLog()
            {
                Id = IdGenerater.GetNextId(),
                Transferee = Transferee.Id,
                Transferor = Transferor.Id,
                Money = input.Money,
                CreateTime = DateTime.Now,
                Remark = input.Remark,
            };

            List<WalletLog> walletLogs = new List<WalletLog>();
            decimal ChangeNoChargeBalance = 0m;
            decimal ChangeChargeBalance = 0m;

            #region 转出
            var cacheKey = "wallet:" + Transferor.Id;
            var flag = await _locker.LockAsync(cacheKey);
            if (!flag.Success)
            {
                await Task.Delay(300);
                flag = await _locker.LockAsync(cacheKey);
            }

            try
            {
                WalletLog walletLog = new WalletLog
                {
                    Id = IdGenerater.GetNextId(),
                    UserId = Transferor.Id,
                    Mode = 5,
                    ChangeAmount = input.Money,
                    Amount = -input.Money,
                    ChangeMode = -1,
                    WalletType = 1,
                    ChangeFrontMoney = fromWallet.Balance,
                    ChangeFrontImage = JsonSerializer.Serialize(fromWallet),
                    Describe = "转出",
                    CreateTime = DateTime.Now,
                    SourceOrder = transferLog.Id
                };
                if (fromWallet.NoChargeBalance < input.Money)
                {
                    ChangeNoChargeBalance = fromWallet.NoChargeBalance;
                    ChangeChargeBalance = input.Money - fromWallet.NoChargeBalance;
                }
                else
                    ChangeNoChargeBalance = input.Money;

                fromWallet.ChargeBalance -= ChangeChargeBalance;
                fromWallet.NoChargeBalance -= ChangeNoChargeBalance;
                fromWallet.Balance -= input.Money;
                walletLog.ChangedLaterImage = JsonSerializer.Serialize(fromWallet);
                walletLog.ChangeLaterMoney = fromWallet.Balance;
                walletLogs.Add(walletLog);

                await _walletRepository.UpdateAsync(fromWallet, UpdatingProps<Wallet>(x => x.Balance, x => x.NoChargeBalance, x => x.ChargeBalance));
            }
            finally
            {
                await _locker.SafedUnLockAsync(cacheKey, flag.LockValue);
            }
            #endregion

            #region 转入
            cacheKey = "wallet:" + Transferee.Id;
            flag = await _locker.LockAsync(cacheKey);
            if (!flag.Success)
            {
                await Task.Delay(300);
                flag = await _locker.LockAsync(cacheKey);
            }

            try
            {
                if (toWallet == null)
                {
                    toWallet = new Wallet
                    {
                        Id = IdGenerater.GetNextId(),
                        UserId = Transferee.Id,
                    };
                    await _walletRepository.InsertAsync(toWallet);
                }

                var walletLog = new WalletLog
                {
                    Id = IdGenerater.GetNextId(),
                    UserId = Transferee.Id,
                    Mode = 5,
                    WalletType = 1,
                    ChangeAmount = input.Money,
                    ChangeMode = 1,
                    Amount = input.Money,
                    ChangeFrontMoney = toWallet == null ? 0 : toWallet.Balance,
                    ChangeFrontImage = JsonSerializer.Serialize(toWallet),
                    Describe = "转入",
                    CreateTime = DateTime.Now,
                    SourceOrder = transferLog.Id,
                };

                toWallet.Balance += input.Money;
                toWallet.NoChargeBalance += ChangeNoChargeBalance;
                toWallet.ChargeBalance += ChangeChargeBalance;
                walletLog.ChangedLaterImage = JsonSerializer.Serialize(toWallet);
                walletLog.ChangeLaterMoney = toWallet.Balance;
                walletLogs.Add(walletLog);

                await _walletRepository.UpdateAsync(toWallet, UpdatingProps<Wallet>(x => x.Balance, x => x.NoChargeBalance, x => x.ChargeBalance));
            }
            finally
            {
                await _locker.SafedUnLockAsync(cacheKey, flag.LockValue);
            }
            #endregion


            await _walletLogRepository.InsertRangeAsync(walletLogs);
            await _walletTransferAccountsLog.InsertAsync(transferLog);

            return true;
        }
    }
}
